/******************************************************************************

	Simple Authentication and Authorisation System (SAAS)

	libnss_saas.c

	Copyright (C) 2011  Josh Goes


	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.

	If you require a mailing address for this software, please send your
	mail to the following address:
	    SAAS
	    GPO Box 2973
	    BRISBANE QLD 4001
	    AUSTRALIA

******************************************************************************/

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <grp.h>
#include <nss.h>
#include <pthread.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#include "saas.h"

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;


// a very helpful option borrowed from the AFS NSS module.
int cpstr( char *str , char **buf , size_t *blen )
{
	// calculate the length of the string given.
	int slen = strlen( str );
	// if we don't have enough room in the buffer for the string, don't bother copying it.
	if ( slen >= *blen-1 )
		return 0;

	// if we reached this far, it'll be a safe copy.
	// copy the string and update the pointers & buffer offsets.
	strcpy( *buf , str );
	*blen -= slen + 1;
	*buf    += slen + 1;

	return slen;
}




//
// GROUP by GID
//
enum nss_status _nss_saas_getgrgid_r( gid_t gid , struct group *result , char *buffer , size_t buflen , int *errnop)
{
	saas_t  inst;
	int ret;
	uint16_t k;
	saas_group_info_t  *group_info = NULL;
	uint32_t  len_req;

	if ( saas_init( &inst , NULL , LOG_INFO , SAAS_LOG_NONE , 0 , NULL ) != SAAS_OP_SUCCESS )
		return NSS_STATUS_UNAVAIL;

	ret = saas_group_info_with_all_members_by_gid( &inst , gid , &group_info );
	switch ( ret )
	{
		case SAAS_OP_SUCCESS:
			// before we progress on-ward, double check that the buffer given has enough room
			// for the user list.
			len_req  = strlen( group_info->groupname );
			len_req += ( group_info->num_members + 1 ) * sizeof( char * );
			if ( len_req >= buflen )
			{
				// we weren't given enough memory for all the data, so report an UNAVAIL.
				*errnop = ENOMEM;
				return NSS_STATUS_UNAVAIL;
			}
			break;

		case SAAS_OP_GROUP_NOT_FOUND:
			if (pthread_mutex_lock(&mutex)) return NSS_STATUS_UNAVAIL;
			pthread_mutex_unlock(&mutex);
			*errnop = ENOENT;
			return NSS_STATUS_NOTFOUND;

		default:
			*errnop = ENOENT;
			return NSS_STATUS_UNAVAIL;
	}

	// copy the username over into the buffer.
	result->gr_name = buffer;
	if ( ! cpstr( group_info->groupname , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	// set the password to "x"
	result->gr_passwd = buffer;
	if ( ! cpstr( "x" , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	// set the UID and GID
	result->gr_gid = group_info->gid;

	// now we set up a list of pointers at the current buffer address.
	result->gr_mem = (char **) buffer;
	// and now we skip forward, leaving enough room for (num_members+1) pointers.
	buffer += ( group_info->num_members + 1 ) * sizeof( char * );
	for ( k = 0 ; k < group_info->num_members ; k++ )
	{
		if ( group_info->members[k]->type != SAAS_GROUP_MEMBER_TYPE_USER )
			continue;
		result->gr_mem[k] = buffer;
		if ( ! cpstr( group_info->members[k]->name , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;
	}

	free( group_info );
	return NSS_STATUS_SUCCESS;
}




//
// GROUP by GROUPNAME
//
enum nss_status _nss_saas_getgrnam_r( char *name , struct group *result , char *buffer , size_t buflen , int *errnop )
{
	saas_t  inst;
	int ret;
	uint16_t k;
	saas_group_info_t  *group_info = NULL;
	uint32_t  len_req;

	if ( saas_init( &inst , NULL , LOG_INFO , SAAS_LOG_NONE , 0 , NULL ) != SAAS_OP_SUCCESS )
		return NSS_STATUS_UNAVAIL;

	ret = saas_group_info_with_all_members_by_groupname( &inst , name , &group_info );
	switch ( ret )
	{
		case SAAS_OP_SUCCESS:
			// before we progress on-ward, double check that the buffer given has enough room
			// for the user list.
			len_req  = strlen( group_info->groupname );
			len_req += ( group_info->num_members + 1 ) * sizeof( char * );
			if ( len_req >= buflen )
			{
				// we weren't given enough memory for all the data, so report an UNAVAIL.
				*errnop = ENOMEM;
				return NSS_STATUS_UNAVAIL;
			}
			break;

		case SAAS_OP_GROUP_NOT_FOUND:
			if (pthread_mutex_lock(&mutex)) return NSS_STATUS_UNAVAIL;
			pthread_mutex_unlock(&mutex);
			*errnop = ENOENT;
			return NSS_STATUS_NOTFOUND;

		default:
			*errnop = ENOENT;
			return NSS_STATUS_UNAVAIL;
	}

	// copy the username over into the buffer.
	result->gr_name = buffer;
	if ( ! cpstr( group_info->groupname , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	// set the password to "x"
	result->gr_passwd = buffer;
	if ( ! cpstr( "x" , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	// set the UID and GID
	result->gr_gid = group_info->gid;

	// now we set up a list of pointers at the current buffer address.
	result->gr_mem = (char **) buffer;
	// and now we skip forward, leaving enough room for (num_members+1) pointers.
	buffer += ( group_info->num_members + 1 ) * sizeof( char * );
	int idx = 0;
	for ( k = 0 ; k < group_info->num_members ; k++ )
	{
		if ( group_info->members[k]->type != SAAS_GROUP_MEMBER_TYPE_USER )
			continue;
		result->gr_mem[idx++] = buffer;
		if ( ! cpstr( group_info->members[k]->name , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;
	}

	free( group_info );
	return NSS_STATUS_SUCCESS;
}




//
// retrieve PASSWD details by UID
//
enum nss_status _nss_saas_getpwuid_r( uid_t uid , struct passwd *result_buf , char *buffer , size_t buflen , int *errnop )
{
	saas_t  inst;
	int ret;
	saas_user_info_t  *user_info = NULL;

	if ( saas_init( &inst , NULL , LOG_INFO , SAAS_LOG_NONE , 0 , NULL ) != SAAS_OP_SUCCESS )
		return NSS_STATUS_UNAVAIL;

	ret = saas_user_info_by_uid( &inst , uid , &user_info );
	switch ( ret )
	{
		case SAAS_OP_SUCCESS:
			break;

		case SAAS_OP_USER_NOT_FOUND:
			if (pthread_mutex_lock(&mutex)) return NSS_STATUS_UNAVAIL;
			pthread_mutex_unlock(&mutex);
			*errnop = ENOENT;
			return NSS_STATUS_NOTFOUND;

		default:
			*errnop = ENOENT;
			return NSS_STATUS_UNAVAIL;
	}

	// copy the username over into the buffer.
	result_buf->pw_name = buffer;
	if ( ! cpstr( user_info->username , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	// set the password to "x"
	result_buf->pw_passwd = buffer;
	if ( ! cpstr( "x" , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	// set the UID and GID
	result_buf->pw_uid = user_info->uid;
	result_buf->pw_gid = user_info->gid;

	// set the gecos field.
	result_buf->pw_gecos = buffer;
	if ( ! cpstr( user_info->displayname , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	// set the home dir.
	result_buf->pw_dir = buffer;
	if ( ! cpstr( user_info->home_dir , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	// set the shell
	result_buf->pw_shell = buffer;
	if ( ! cpstr( user_info->shell , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	free( user_info );
	return NSS_STATUS_SUCCESS;
}



//
// retrieve PASSWD details by USERNAME
//
enum nss_status _nss_saas_getpwnam_r( char *name , struct passwd *result_buf , char *buffer , size_t buflen , int *errnop )
{
	saas_t  inst;
	int ret;
	saas_user_info_t  *user_info = NULL;

	if ( saas_init( &inst , NULL , LOG_INFO , SAAS_LOG_NONE , 0 , NULL ) != SAAS_OP_SUCCESS )
		return NSS_STATUS_UNAVAIL;

	ret = saas_user_info_by_username( &inst , name , &user_info );
	switch ( ret )
	{
		case SAAS_OP_SUCCESS:
			break;

		case SAAS_OP_USER_NOT_FOUND:
			if (pthread_mutex_lock(&mutex)) return NSS_STATUS_UNAVAIL;
			pthread_mutex_unlock(&mutex);
			*errnop = ENOENT;
			return NSS_STATUS_NOTFOUND;

		default:
			*errnop = ENOENT;
			return NSS_STATUS_UNAVAIL;
	}

	// copy the username over into the buffer.
	result_buf->pw_name = buffer;
	if ( ! cpstr( user_info->username , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	// set the password to "x"
	result_buf->pw_passwd = buffer;
	if ( ! cpstr( "x" , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	// set the UID and GID.
	result_buf->pw_uid = user_info->uid;
	result_buf->pw_gid = user_info->gid;

	// set the gecos field.
	result_buf->pw_gecos = buffer;
	if ( ! cpstr( user_info->displayname , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	// set the home dir.
	result_buf->pw_dir = buffer;
	if ( ! cpstr( user_info->home_dir , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	// set the shell.
	result_buf->pw_shell = buffer;
	if ( ! cpstr( user_info->shell , &buffer , &buflen ) ) return NSS_STATUS_UNAVAIL;

	free( user_info );
	return NSS_STATUS_SUCCESS;
}


enum nss_status _nss_saas_initgroups_dyn( const char *user , gid_t group , long int *start , long int *size , gid_t ** groups , long int limit , int *errnop )
{
	gid_t *grps = *groups;

	saas_t  inst;
	int ret;
	uint16_t k;
	saas_user_info_t  *user_info = NULL;

	if ( saas_init( &inst , NULL , LOG_INFO , SAAS_LOG_NONE , 0 , NULL ) != SAAS_OP_SUCCESS )
		return NSS_STATUS_UNAVAIL;

	ret = saas_user_info_with_all_groups_by_username( &inst , user , &user_info );
	switch ( ret )
	{
		case SAAS_OP_SUCCESS:
			for ( k = 0 ; k < user_info->num_groups ; k++ )
			{
				if ( group != user_info->groups[k]->gid )
				{
					grps[*start] = user_info->groups[k]->gid;
					++*start;
				}
			}
			break;

		case SAAS_OP_USER_NOT_FOUND:
			if (pthread_mutex_lock(&mutex)) return NSS_STATUS_UNAVAIL;
			pthread_mutex_unlock(&mutex);
			*errnop = ENOENT;
			return NSS_STATUS_NOTFOUND;

		default:
			*errnop = ENOENT;
			return NSS_STATUS_UNAVAIL;
	}


	*errnop = 0;
	return NSS_STATUS_SUCCESS;
}

